﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Weazel.Willows.Model
{
	public partial class Module 
		: HierarchicalEntity
	{
		private string uniqueName;
		public string UniqueName
		{
			get { return uniqueName; }
			set { uniqueName = value; }
		}

		protected TypeRegistry typeRegistry;
		public TypeRegistry TypeRegistry
		{
			get { return typeRegistry; }
		}

		private PartitionChoice partitionChoice;
		public PartitionChoice PartitionChoice
		{
			get { return partitionChoice; }
		}

		private bool isHardwired;
		public bool IsHardwired
		{
			get { return isHardwired; }
			set { isHardwired = value; }
		}

		/// <summary>
		/// Child modules (datapath/controller pairs) of this module
		/// </summary>
		private Dictionary<string, Module> children;
		public Dictionary<string, Module> Children
		{
			get { return children; }			
		}

		private Dictionary<string, Channel> channels;
		public Dictionary<string, Channel> Channels
		{
			get { return channels; }
		}

		private ConnectorCollection connectors;
		public ConnectorCollection Connectors
		{
			get { return connectors; }
		}

		//private List<ChannelReaderInterface> readers = new List<ChannelReaderInterface>();
		//public List<ChannelReaderInterface> Readers
		//{
		//  get { return readers; }
		//  set { readers = value; }
		//}

		private Dictionary<string, ChannelReaderInterface> readers = new Dictionary<string, ChannelReaderInterface>();
		public Dictionary<string, ChannelReaderInterface> Readers
		{
			get { return readers; }
			set { readers = value; }
		}

		private List<ChannelWriterInterface> writers = new List<ChannelWriterInterface>();
		public List<ChannelWriterInterface> Writers
		{
			get { return writers; }
			set { writers = value; }
		}

		private Dictionary<string, Register> registers;
		public Dictionary<string, Register> Registers
		{
			get { return registers; }
		}

		private Dictionary<string, SignalFlowGraph> signalFlowGraphs;
		public Dictionary<string, SignalFlowGraph> SignalFlowGraphs
		{
			get { return signalFlowGraphs; }
		}

		protected Dictionary<string, State> states;
		public Dictionary<string, State> States
		{
			get { return states; }
		}

		protected State initialState;
		public State InitialState
		{
			get { return initialState; }
		}

		private List<TransitionSet> transitionSets;
		public List<TransitionSet> TransitionSets
		{
			get { return transitionSets; }			
		}

		/// <summary>
		/// The "always" sfg is scheduled in every cycle
		/// </summary>
		private SignalFlowGraph alwaysSignalFlowGraph;
		public SignalFlowGraph AlwaysSignalFlowGraph
		{
			get { return alwaysSignalFlowGraph; }
			protected set { alwaysSignalFlowGraph = value; }
		}

		#region Constructors and Initialization
		public Module(IHierarchicalEntity parent, string name, string uniqueName)
			: base(parent, name)
		{
			// initialize data-structures
			initialize();
			this.uniqueName = uniqueName;
		}

		protected Module(IHierarchicalEntity parent)
			: base(parent)
		{
			// initialize data-structures
			initialize();			
		}

		/// <summary>
		/// Initializes datastructures within the class. Should
		/// only be called by the constructors
		/// </summary>
		private void initialize()
		{
			// create the child module map
			this.children = new Dictionary<string, Module>();

			// create the channel map
			this.channels = new Dictionary<string, Channel>();

			// create the connectors map
			this.connectors = new ConnectorCollection(this);

			// create registers map
			this.registers = new Dictionary<string, Register>();

			this.signalFlowGraphs = new Dictionary<string, SignalFlowGraph>();

			this.states = new Dictionary<string, State>();

			this.transitionSets = new List<TransitionSet>();

			this.readers = new Dictionary<string, ChannelReaderInterface>();

			this.writers = new List<ChannelWriterInterface>();

			this.alwaysSignalFlowGraph = new SignalFlowGraph(this, "always");
			SignalFlowGraphs.Add("always", this.alwaysSignalFlowGraph);
		}
		#endregion

		/// <summary>
		/// Returns true if the part of the hiearchy with this
		/// module as a root has the same partition choice
		/// </summary>
		private bool HasChildrenInOtherPartitions
		{
			get
			{
				foreach (Module child in children.Values)
					if (child.HasChildrenInOtherPartitions || child.partitionChoice != this.partitionChoice)
						return true;

				return false;
			}
		}

		public void Partition()
		{
			Console.WriteLine("Partitioning module '{0}'", LocalName);

			MoveQueues();
			MoveModules();

			Module[] childrenCopy = new Module[children.Count];
			children.Values.CopyTo(childrenCopy, 0);

			foreach (Module child in childrenCopy)
			{
				if (child == children["hardware"] || child == children["software"])
					continue;

				if (child.partitionChoice == PartitionChoice.Hardware)
				{
					child.MoveDownInHierarchy(Children["hardware"]);
				}
				else if (child.partitionChoice == PartitionChoice.Software)
				{
					child.MoveDownInHierarchy(Children["software"]);
				}
				else
					throw new NotSupportedException();
			}
		}

		public void MoveModules()
		{
			Module[] childrenCopy = new Module[children.Count];
			children.Values.CopyTo(childrenCopy, 0);

			foreach (Module child in childrenCopy)
				child.MoveModules();

			if (!(Parent is HierarchyRoot)) // || Parent.Parent is HierarchyRoot))
			{
				foreach (Module child in childrenCopy)
				{
					child.MoveUpInHierarchy();
				}
			}
		}

		public void MoveQueues()
		{
			Module[] childrenCopy = new Module[children.Count];
			children.Values.CopyTo(childrenCopy, 0);

			foreach (Module child in childrenCopy)
				child.MoveQueues();

			if (!(Parent is HierarchyRoot)) // || Parent.Parent is HierarchyRoot))
			{
				Channel[] channelsCopy = new Channel[channels.Count];
				channels.Values.CopyTo(channelsCopy, 0);

				foreach (Channel channel in channelsCopy)
					if (channel is Queue)
					{
						Queue queue = channel as Queue;
						queue.MoveUpInHiearchy();
					}
			}
		}



		public void MoveUpInHierarchy()
		{
			if (Parent.Parent is HierarchyRoot)
			{
				// If this datapath is a child of a system it cannot be
				// moved up. A 'system' is the top of the hierarchy.

				string message =
					string.Format("MoveUpInHierarchy(): Cannot move module beyond top level system '{0}'.", Parent.Parent);

				throw new NotSupportedException(message);
			}

			// if the hierarchical parent is not system it must be datapath
			System.Diagnostics.Trace.Assert(Parent is Module);

			Module parentModule = Parent as Module;

			// find out if the new parent of this datapath is a 'system'
			// or another 'datapath'
			if (parentModule.Parent is Module)
			{
				Module newParentModule =
					parentModule.Parent as Module;

				// remove this datapath from the old parent
				parentModule.children.Remove(LocalName);
				
				// create a new localname for this datapath (making sure it is unique)
				LocalName = parentModule.LocalName + "_" + LocalName;
				
				// make this dp a child of the new parent
				newParentModule.children.Add(LocalName, this);

				Parent = Parent.Parent;

				foreach (IConnector connector in connectors.Values)
					connector.DatapathMovedUpInHierarchy(newParentModule, parentModule);
			}
			else
			{
				// unexpected
				throw new NotSupportedException("Unexpected hierarchical parent Type");
			}
		}

		/// <summary>
		/// Push this datapath down in the structural hierarchy. The argument
		/// "newParent" must be a datapath at the same hierarchical level as
		/// the present datapath.
		/// </summary>
		/// <param name="newParent">New parent of this datapath</param>
		public void MoveDownInHierarchy(Module newParent)
		{
			if (Parent is Module)
			{
				Module parentModule = Parent as Module;

				if (!parentModule.Children.ContainsValue(newParent))
				{
					string message =
						string.Format("MoveDownInHierarchy(): '{0}' is not a child datapath of datapath '{1}'.",
						newParent.Name,
						parentModule.Name
						);

					throw new Exception(message);
				}

				// remove the module from the present hierarchy level
				parentModule.children.Remove(this.LocalName);

				// insert the module into the new hierarchy level
				newParent.children.Add(this.LocalName, this);

				// set new parent
				this.Parent = newParent;

				foreach (IConnector connector in connectors.Values)
					connector.DatapathMovedDownInHierarchy(newParent, parentModule);
			}

		}

		public void MoveQueuesToTop()
		{
			foreach (Module child in children.Values)
				child.MoveQueuesToTop();

			if (!(Parent is HierarchyRoot))
			{
				Channel[] channelsCopy = new Channel[channels.Count];
				channels.Values.CopyTo(channelsCopy, 0);

				foreach (Channel channel in channelsCopy)
					if (channel is Queue)
					{
						Queue queue = channel as Queue;
						queue.MoveUpInHiearchy();
					}
			}
		}

		public void SetPartitionChoice(Dictionary<string, PartitionChoice> partition)
		{
			// Get the partition choice for this module. If the local name of 
			// this module is in the list then we use that choice.
			PartitionChoice choice =
					partition.ContainsKey(uniqueName) ? partition[uniqueName]				
				: PartitionChoice.Undefined;

			if (choice == PartitionChoice.Undefined || choice == PartitionChoice.Parent)
			{
				if (Parent is Module)
				{
					Module parentModule = Parent as Module;

					if (parentModule.partitionChoice == PartitionChoice.Software)
						this.partitionChoice = PartitionChoice.Software;
					else if (parentModule.partitionChoice == PartitionChoice.Hardware)
						this.partitionChoice = PartitionChoice.Hardware;
					else
						// unexpected
						throw new NotSupportedException();
				}
				else
				{
					// this must be the top module (must be assigned to a partition) 
					string message =
						string.Format("Missing partition choice for top module '{0}'", LocalName);

					throw new NotSupportedException(message);
				}
			}
			else if (choice == PartitionChoice.Hardware)
			{
				this.partitionChoice = PartitionChoice.Hardware;
			}
			else if (choice == PartitionChoice.Software)
			{
				this.partitionChoice = PartitionChoice.Software;
			}
			else
			{
				throw new NotSupportedException("Partition(): Unsupported partition choice");
			}

			Console.WriteLine("{0}: {1}", LocalName, this.partitionChoice);

			// paritition children
			foreach (Module child in children.Values)
				child.SetPartitionChoice(partition);
		}

		/// <summary>
		/// Given an identifier this method will return the component
		/// with that name (or null if not found)
		/// </summary>
		/// <param name="identifier">identifier to look for</param>
		/// <returns>component</returns>
		protected IHierarchicalEntity LookupIdentifier(string identifier)
		{
			// check if the identifier is a connector
			if (connectors.ContainsKey(identifier))
				return connectors[identifier];
			// check if the identifier is a channel
			else if (channels.ContainsKey(identifier))
				return channels[identifier];
			// check if the identifier is a register
			else if (Registers.ContainsKey(identifier))
				return Registers[identifier];
			// identifier not found 
			return null;
		}

		public void WriteChannelHierarchy(System.CodeDom.Compiler.IndentedTextWriter textWriter)
		{
			textWriter.WriteLine("+====================================================================+");
			textWriter.WriteLine(" Module LocalName: '{0}' Name: '{1}' Parent '{2}'",
				LocalName,
				Name,
				Parent is HierarchyRoot ? "None" : Parent.LocalName
			);
			textWriter.WriteLine("+====================================================================+");
			textWriter.Indent++;

			foreach (Channel channel in Channels.Values)
			{
				channel.WriteChannelHierarchy(textWriter);
			}
			textWriter.Indent--;

			// write child modules
			textWriter.WriteLine("CHILD MODULES:");
			textWriter.Indent++;
			if (children.Count == 0)
			{
				textWriter.WriteLine("No child modules");
			}
			else
			{
				foreach (Module child in children.Values)
					child.WriteChannelHierarchy(textWriter);
			}
			textWriter.Indent--;
		}

		public void WriteHierarchy(System.CodeDom.Compiler.IndentedTextWriter textWriter)
		{
			textWriter.WriteLine("+====================================================================+");
			textWriter.WriteLine(" Module LocalName: '{0}' Name: '{1}' Parent '{2}'", 
				LocalName, 
				Name, 
				Parent is HierarchyRoot ? "None" : Parent.LocalName
			);
			textWriter.WriteLine("+====================================================================+");

			// states 
			textWriter.WriteLine("STATES:");
			textWriter.Indent++;
			if (States.Count == 0)
			{
				textWriter.WriteLine("No States");
			}
			else
			{
				foreach (State state in States.Values)
					textWriter.WriteLine("Name: {0} (initial: {1})", state.LocalName, state.IsInitial);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// transition sets 
			textWriter.WriteLine("Transitions:");
			textWriter.Indent++;
			if (this.transitionSets.Count == 0)
			{
				textWriter.WriteLine("No transitions");
			}
			else
			{
				foreach (TransitionSet set in transitionSets)
					set.WriteTo(textWriter);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");


			textWriter.WriteLine("PARTITION CHOICE: ");
			textWriter.Indent++;
			textWriter.WriteLine(partitionChoice == null ? "not set" : partitionChoice.ToString());
			textWriter.Indent--;

			// write sfg's
			textWriter.WriteLine("SIGNAL FLOW GRAPHS:");
			textWriter.Indent++;
			if (signalFlowGraphs.Count == 0)
			{
				textWriter.WriteLine("No sfgs");
			}
			else
			{
				foreach (SignalFlowGraph sfg in SignalFlowGraphs.Values)
					textWriter.WriteLine("Name: {0}", sfg.LocalName);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// write channels
			textWriter.WriteLine("CHANNELS:");			
			textWriter.Indent++;
			if (channels.Count == 0)
			{
				textWriter.WriteLine("No channels");
			}
			else
			{
				foreach (Channel channel in channels.Values)
					textWriter.WriteLine("Name: {0} parent: {1} Type: {2}", channel.LocalName, channel.Parent.LocalName, channel.GetType().Name);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// write connectors
			textWriter.WriteLine("CONNECTORS:");
			textWriter.Indent++;
			if (connectors.Count == 0)
			{
				textWriter.WriteLine("No Connectors");
			}
			else
			{
				foreach (IConnector connector in connectors.Values)
					textWriter.WriteLine("Name: {0} parent: {1} Type: {2}", connector.Name, connector.Parent.LocalName, connector.GetType().Name);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// write the names of the identifers read by the sfg's in this module
			textWriter.WriteLine("IDENTIFIERS READ:");
			textWriter.Indent++;
			if (readers == null)
			{
				textWriter.WriteLine("Readers not set");
			}
			else if (readers.Count == 0)
			{
				textWriter.WriteLine("None");
			}
			else
			{
				foreach (ChannelReaderInterface @interface in readers.Values)
					textWriter.WriteLine("Name: {0} parent: {1} Type: {2}", @interface.LocalName, @interface.Parent.LocalName, @interface.GetType().Name);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// write the names of the identifers written by the sfg's in this module
			textWriter.WriteLine("IDENTIFIERS WRITTEN:");			
			textWriter.Indent++;
			if (writers == null)
			{
				textWriter.WriteLine("Writers not set");
			}
			else if (writers.Count == 0)
			{
				textWriter.WriteLine("None");
			}
			else
			{
				foreach (ChannelWriterInterface @interface in writers)
					textWriter.WriteLine("Name: {0} parent: {1} Type: {2}", @interface.LocalName, @interface.Parent.LocalName, @interface.GetType().Name);
			}
			textWriter.Indent--;
			textWriter.WriteLine("+--------------------------------------------------------------------+");

			// write child modules
			textWriter.WriteLine("CHILD MODULES:");			
			textWriter.Indent++;
			if (children.Count == 0)
			{
				textWriter.WriteLine("No child modules");
			}
			else
			{
				foreach (Module child in children.Values)
					child.WriteHierarchy(textWriter);
			}
			textWriter.Indent--;
		}





	}
}
